#include "stdafx.h"

#include "Usb.h"

#include <SetupAPI.h>

bool OpenHidDevice( USHORT const VendorID, USHORT const ProductID, USHORT const VersionNumber, HANDLE& hInputHandle, HANDLE& hOutputHandle ) {
	DWORD dwLastError;

	//
	// Step 1: Get the class device GUID for the HID subsystem.
	//

	GUID HidGuid;
	HidD_GetHidGuid( &HidGuid );

	//
	// Step 2: Get the class device.
	//

	SetLastError( 0 );
	HDEVINFO hDevInfo = SetupDiGetClassDevs( &HidGuid, nullptr, nullptr, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT );
	if ( INVALID_HANDLE_VALUE == hDevInfo ) {
		dwLastError = GetLastError( );
		debug( L"OpenHidDevice: SetupDiGetClassDevs returned INVALID_HANDLE_VALUE, error is %ld\n", dwLastError );
		BreakIfDebuggerPresent( );
		return false;
	}

	//
	// Steps 3 and 4: Enumerate the class device's interfaces, then get the device interface's details.
	//

	DWORD DetailDataSize = offsetof( SP_DEVICE_INTERFACE_DETAIL_DATA, DevicePath ) + sizeof( wchar_t ) * 32768;
	int MemberIndex = 0;
	DWORD RequiredSize;
	wstring driverFileName;
	SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
	SP_INTERFACE_DEVICE_DETAIL_DATA* pInterfaceDeviceDetailData = reinterpret_cast< SP_INTERFACE_DEVICE_DETAIL_DATA* >( new UINT8[DetailDataSize] );
	SP_DEVINFO_DATA DeviceInfoData;

	HANDLE hInputDevice  = INVALID_HANDLE_VALUE;
	HANDLE hOutputDevice = INVALID_HANDLE_VALUE;

	while ( true ) {
		ZeroMemory( DeviceInterfaceData );
		DeviceInterfaceData.cbSize = sizeof( SP_DEVICE_INTERFACE_DATA );

		SetLastError( 0 );
		if ( !SetupDiEnumDeviceInterfaces( hDevInfo, nullptr, &HidGuid, MemberIndex, &DeviceInterfaceData ) ) {
			dwLastError = GetLastError( );
			if ( ERROR_NO_MORE_ITEMS == dwLastError ) {
				break;
			}
			debug( L"OpenHidDevice: SetupDiEnumDeviceInterfaces returned FALSE, error is %ld\n", dwLastError );
			BreakIfDebuggerPresent( );
			goto next0;
		}

		ZeroMemory( pInterfaceDeviceDetailData, DetailDataSize );
		pInterfaceDeviceDetailData->cbSize = sizeof( SP_INTERFACE_DEVICE_DETAIL_DATA );
		RequiredSize = 0;
		DeviceInfoData.cbSize = sizeof( SP_DEVINFO_DATA );

		SetLastError( 0 );
		if ( !SetupDiGetDeviceInterfaceDetail( hDevInfo, &DeviceInterfaceData, pInterfaceDeviceDetailData, DetailDataSize, &RequiredSize, &DeviceInfoData ) ) {
			dwLastError = GetLastError( );
			debug( L"OpenHidDevice: SetupDiGetDeviceInterfaceDetail returned FALSE, error is %ld\n" );
			BreakIfDebuggerPresent( );
			goto next0;
		}

		//
		// Step 5: Open the device.
		//

		// 5a: for input...

		SetLastError( 0 );
		hInputDevice = CreateFile( pInterfaceDeviceDetailData->DevicePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr );
		if ( INVALID_HANDLE_VALUE == hInputDevice ) {
			dwLastError = GetLastError( );
			debug( L"OpenHidDevice: CreateFile for input device returned INVALID_HANDLE_VALUE, error is %ld\n", dwLastError );
			goto next0;
		}

		//
		// Step 6: Query the device's attributes to figure out if it's the device we want.
		//

		HIDD_ATTRIBUTES Attributes;
		ZeroMemory( Attributes );

		SetLastError( 0 );
		if ( !HidD_GetAttributes( hInputDevice, &Attributes ) ) {
			dwLastError = GetLastError( );
			debug( L"HidD_GetAttributes failed, error %ld\n", dwLastError );
			BreakIfDebuggerPresent( );
			goto next1;
		}
		debug( L"VID 0x%04hx PID 0x%04hx VN %hd\n", Attributes.VendorID, Attributes.ProductID, Attributes.VersionNumber );
		if ( Attributes.VendorID == VendorID && Attributes.ProductID == ProductID && Attributes.VersionNumber == VersionNumber ) {
			debug( L"OpenHidDevice: Found Panic Button device!\n" );

			// 5b: for output...

			SetLastError( 0 );
			hOutputDevice = CreateFile( pInterfaceDeviceDetailData->DevicePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr );
			if ( INVALID_HANDLE_VALUE == hOutputDevice ) {
				dwLastError = GetLastError( );
				debug( L"OpenHidDevice: CreateFile for output device returned INVALID_HANDLE_VALUE, error is %ld\n", dwLastError );
				goto next1;
			}

			break;
		}

next1:
		CloseHandle( hInputDevice );
		hInputDevice = INVALID_HANDLE_VALUE;
next0:
		MemberIndex++;
	}

	delete pInterfaceDeviceDetailData;

	if ( hInputDevice != INVALID_HANDLE_VALUE && hOutputDevice != INVALID_HANDLE_VALUE ) {
		hInputHandle = hInputDevice;
		hOutputHandle = hOutputDevice;
		return true;
	}

	CloseHandleIfNotEqual( hInputHandle,  INVALID_HANDLE_VALUE );
	CloseHandleIfNotEqual( hOutputHandle, INVALID_HANDLE_VALUE );
	debug( L"OpenHidDevice: Couldn't find a Panic Button device.\n" );
	BreakIfDebuggerPresent( );
	return false;
}

bool GetDeviceCapabilities( HANDLE const hInputDevice, HIDP_CAPS& Capabilities ) {
	DWORD dwLastError = ERROR_SUCCESS;
	wchar_t buffer[4096];

	//
	// Fetch information about the device
	//

	SetLastError( 0 );
	if ( !HidD_GetManufacturerString( hInputDevice, buffer, 4095 ) ) {
		dwLastError = GetLastError( );
		debug( L"GetDeviceCapabilities: HidD_GetManufacturerString failed, error %ld; continuing\n", dwLastError );
	} else {
		debug( L"GetDeviceCapabilities: Device manufacturer string: '%s'\n", buffer );
	}

	SetLastError( 0 );
	if ( !HidD_GetProductString( hInputDevice, buffer, 4095 ) ) {
		dwLastError = GetLastError( );
		debug( L"GetDeviceCapabilities: HidD_GetProductString failed, error %ld; continuing\n", dwLastError );
	} else {
		debug( L"GetDeviceCapabilities: Device product string: '%s'\n", buffer );
	}
	
#if 0
	// device doesn't have a serial number

	SetLastError( 0 );
	if ( !HidD_GetSerialNumberString( hInputDevice, buffer, 4095 ) ) {
		dwLastError = GetLastError( );
		debug( L"GetDeviceCapabilities: HidD_GetSerialNumberString failed, error %ld; continuing\n", dwLastError );
	} else {
		debug( L"GetDeviceCapabilities: Device serial number string: '%s'\n", buffer );
	}
#endif

	//
	// Get the preparsed data required by a number of other functions.
	//

	PHIDP_PREPARSED_DATA PreparsedData = nullptr;
	SetLastError( 0 );
	if ( !HidD_GetPreparsedData( hInputDevice, &PreparsedData ) ) {
		dwLastError = GetLastError( );
		debug( L"GetDeviceCapabilities: HidD_GetPreparsedData failed, error is %ld\n", dwLastError );
		BreakIfDebuggerPresent( );
		return false;
	}

	//
	// Get device capabilities
	//

	ZeroMemory( Capabilities );
	NTSTATUS nsret = HidP_GetCaps( PreparsedData, &Capabilities );
	if ( HIDP_STATUS_SUCCESS != nsret ) {
		debug( L"HidPGetCaps: failed, error 0x%08x\n", nsret );
		BreakIfDebuggerPresent( );
		HidD_FreePreparsedData( PreparsedData );
		return false;
	}

	HidD_FreePreparsedData( PreparsedData );
	return true;
}
